import yfinance as yf
import akshare as ak
import requests
import time
import json
import os
import datetime
from collections import defaultdict


# ================== 配置目录管理 ==================
def setup_config_path():
    # 创建配置目录（如果使用相对路径）
    config_dir = os.path.dirname(ConfigManager.CONFIG_FILE)

    # 确保目录存在
    if config_dir and not os.path.exists(config_dir):
        os.makedirs(config_dir, exist_ok=True)
        print(f"已创建配置目录: {config_dir}")

    # 确保文件存在
    if not os.path.exists(ConfigManager.CONFIG_FILE):
        print(f"配置文件不存在，创建新文件: {ConfigManager.CONFIG_FILE}")
        ConfigManager.save_config(ConfigManager.load_config())

# ================== 配置模块 ==================
class ConfigManager:
    CONFIG_FILE = "monitor_config.json"

    @staticmethod
    def load_config():
        """加载配置文件"""
        if os.path.exists(ConfigManager.CONFIG_FILE):
            with open(ConfigManager.CONFIG_FILE, 'r') as f:
                return json.load(f)
        return {
            "monitor_list": {},
            "wechat_webhook": "",
            "check_interval": 60,
            "max_alerts": 10,
            "max_cache_seconds": 300  # 数据缓存时间(秒)
        }

    @staticmethod
    def save_config(config):
        """保存配置文件"""
        with open(ConfigManager.CONFIG_FILE, 'w') as f:
            json.dump(config, f, indent=4)


# ================== 监控逻辑模块 ==================
class PriceMonitor:
    def __init__(self):
        self.config = ConfigManager.load_config()
        self.price_cache = defaultdict(dict)  # 缓存最新价格 {"510300": {"price": 3.52, "time": timestamp}}
        self.last_fetch_time = {}  # 记录上次全量获取ETF数据的时间

    def add_to_monitor(self, symbol, entry_price, market=None):
        """添加监控标的并自动检测市场类型"""
        # 自动检测市场类型
        if not market:
            market = self.detect_market_type(symbol)

        self.config['monitor_list'][symbol] = {
            'entry_price': float(entry_price),
            'next_alert_price': float(entry_price) * 0.96,
            'alert_count': 0,
            'market': market  # "us"/"hk"/"a_etf"/"a_stock"
        }
        ConfigManager.save_config(self.config)
        return market

    def detect_market_type(self, symbol):
        """根据代码自动检测市场类型"""
        # A股ETF: 6位数字代码(510/511/512/515/588开头)
        if len(symbol) == 6 and symbol.isdigit():
            if symbol.startswith(("510", "511", "512", "515", "588")):
                return "a_etf"
            return "a_stock"
        # A股股票: 6位数字代码(0/3/6开头)
        elif len(symbol) == 6 and symbol.isdigit():
            return "a_stock"
        # 美股/HK股票: 字母代码
        elif symbol.isalpha():
            return "us" if not symbol.endswith(".HK") else "hk"
        # 港股: 带.HK后缀
        elif symbol.upper().endswith(".HK"):
            return "hk"
        else:
            return "us"  # 默认为美股

    def remove_from_monitor(self, symbol):
        """移除监控标的"""
        if symbol in self.config['monitor_list']:
            del self.config['monitor_list'][symbol]
            ConfigManager.save_config(self.config)

    def reset_alert_count(self, symbol):
        """重置标的的触发计数"""
        if symbol in self.config['monitor_list']:
            self.config['monitor_list'][symbol]['alert_count'] = 0
            self.config['monitor_list'][symbol]['next_alert_price'] = (
                    self.config['monitor_list'][symbol]['entry_price'] * 0.96
            )
            ConfigManager.save_config(self.config)

    def get_current_price(self, symbol, market_type):
        """根据市场类型获取标的实时价格(带缓存机制)"""
        # 检查缓存是否有效
        cache_data = self.price_cache.get(symbol, {})
        if cache_data:
            cache_time = cache_data.get("timestamp", 0)
            current_time = time.time()
            if current_time - cache_time < self.config.get("max_cache_seconds", 300):
                return cache_data.get("price", None)

        price = None

        try:
            if market_type == "a_etf":
                # 优化：每5分钟只获取一次全量ETF数据
                if symbol not in self.last_fetch_time or time.time() - self.last_fetch_time[symbol] > 300:
                    df = ak.fund_etf_spot_em()
                    self.last_fetch_time[symbol] = time.time()

                    # 更新所有A股ETF缓存
                    for _, row in df.iterrows():
                        etf_code = row["代码"]
                        etf_price = row["最新价"]
                        if isinstance(etf_price, str):
                            etf_price = float(etf_price.replace(",", ""))
                        self.price_cache[etf_code] = {
                            "price": etf_price,
                            "timestamp": time.time()
                        }

                # 从缓存中获取价格
                price = self.price_cache.get(symbol, {}).get("price")

            elif market_type == "a_stock":
                # 使用AKShare获取A股股票
                if symbol.startswith(("6", "9")):
                    symbol_with_prefix = f"sh{symbol}"
                else:
                    symbol_with_prefix = f"sz{symbol}"

                df = ak.stock_zh_a_spot_em(symbol=symbol_with_prefix)
                if not df.empty:
                    price = df["最新价"].iloc[0]

            else:  # 美股/港股使用yfinance
                # 适配港股代码后缀
                if market_type == "hk" and not symbol.endswith(".HK"):
                    symbol += ".HK"

                ticker = yf.Ticker(symbol)
                data = ticker.history(period="1d", interval="1m")
                if not data.empty:
                    price = data["Close"].iloc[-1]

        except Exception as e:
            print(f"获取 {symbol}({market_type}) 价格失败: {str(e)[:100]}")

        # 更新缓存
        if price:
            self.price_cache[symbol] = {
                "price": price,
                "timestamp": time.time()
            }

        return price


# ================== 通知模块 ==================
class WeChatNotifier:
    @staticmethod
    def send_alert(symbol, current_price, alert_price, alert_count, max_alerts, webhook_url):
        """发送微信加仓提醒(支持中文ETF名称)"""
        # 尝试获取ETF中文名称
        try:
            if ak.fund_etf_spot_em is not None:
                df = ak.fund_etf_spot_em()
                etf_info = df[df["代码"] == symbol]
                if not etf_info.empty:
                    symbol_name = f"{symbol}({etf_info['名称'].values[0]})"
                else:
                    symbol_name = symbol
            else:
                symbol_name = symbol
        except:
            symbol_name = symbol

        message = (
            f"🚨【加仓提醒】{symbol_name}\n"
            f"📉 当前价格: {current_price:.4f}\n"
            f"🎯 触发价位: {alert_price:.4f}\n"
            f"⏱️ 本次触发: 第{alert_count}次\n"
            f"🔢 剩余次数: {max_alerts - alert_count}次\n"
            f"⏰ 时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
            f"💡 建议: 在合理估值区适时加仓"
        )

        payload = {
            "msgtype": "text",
            "text": {"content": message}
        }

        try:
            response = requests.post(webhook_url, json=payload, timeout=10)
            return response.status_code == 200
        except Exception as e:
            print(f"微信通知发送失败: {e}")
            return False


# ================== 主监控循环 ==================
def run_monitor():
    monitor = PriceMonitor()
    notifier = WeChatNotifier()

    while True:
        config = monitor.config
        webhook_url = config.get('wechat_webhook', '')
        max_alerts = config.get('max_alerts', 10)

        if not webhook_url:
            print("未配置微信Webhook！")
            time.sleep(10)
            continue

        for symbol, data in config['monitor_list'].items():
            market_type = data.get('market', monitor.detect_market_type(symbol))
            current_price = monitor.get_current_price(symbol, market_type)

            if current_price is None:
                print(f"无法获取 {symbol} 价格，跳过本次检查")
                continue

            entry_price = data['entry_price']
            next_alert_price = data['next_alert_price']
            alert_count = data['alert_count']

            # 触发加仓条件
            if current_price <= next_alert_price and alert_count < max_alerts:
                # 发送通知
                success = notifier.send_alert(
                    symbol, current_price, next_alert_price,
                    alert_count + 1, max_alerts, webhook_url
                )

                if success:
                    # 更新触发状态
                    data['alert_count'] += 1
                    data['next_alert_price'] = next_alert_price * 0.96
                    print(f"{symbol} 第{data['alert_count']}次加仓提醒已发送")

                    # 保存更新后的配置
                    ConfigManager.save_config(config)

            # 重置检测：价格回升至成本价时
            elif current_price >= entry_price and alert_count > 0:
                monitor.reset_alert_count(symbol)
                print(f"{symbol} 价格回升至成本价，重置加仓计数")

        print(
            f"本轮监控完成，下次检查时间: {datetime.datetime.now() + datetime.timedelta(seconds=config.get('check_interval', 60))}")
        time.sleep(config.get('check_interval', 60))


# ================== 用户交互界面 ==================
def user_interface():
    monitor = PriceMonitor()

    while True:
        print("\n===== 多市场标的分级加仓监控系统 (支持A股ETF) =====")
        print("1. 添加监控标的")
        print("2. 移除监控标的")
        print("3. 设置微信Webhook")
        print("4. 设置最大加仓次数")
        print("5. 查看当前监控列表")
        print("6. 启动监控")
        print("7. 手动获取标的实时价格")
        print("8. 退出")

        choice = input("请选择操作: ")

        if choice == '1':
            symbol = input("输入标的代码 (如510300或AAPL): ")
            entry_price = float(input("输入入场成本价: "))
            market_type = monitor.add_to_monitor(symbol, entry_price)
            print(f"已添加{symbol}，市场类型: {market_type}，初始成本价: {entry_price:.4f}")

        elif choice == '2':
            symbol = input("输入要移除的标的代码: ")
            monitor.remove_from_monitor(symbol)
            print(f"已移除{symbol}")

        elif choice == '3':
            webhook = input("输入企业微信机器人Webhook URL: ")
            monitor.config['wechat_webhook'] = webhook
            ConfigManager.save_config(monitor.config)
            print("Webhook已更新")

        elif choice == '4':
            max_alerts = int(input("设置最大加仓次数: "))
            monitor.config['max_alerts'] = max_alerts
            ConfigManager.save_config(monitor.config)
            print(f"最大加仓次数设置为{max_alerts}次")

        elif choice == '5':
            print("\n当前监控列表:")
            for symbol, data in monitor.config['monitor_list'].items():
                print(f"代码: {symbol} | 市场: {data.get('market')}")
                print(f"  成本价: {data['entry_price']:.4f}")
                print(
                    f"  下次触发: {data['next_alert_price']:.4f} (下跌{100 * (1 - data['next_alert_price'] / data['entry_price']):.2f}%)")
                print(f"  已触发: {data['alert_count']}/{monitor.config.get('max_alerts', 3)}次\n")

        elif choice == '6':
            print("启动监控... (按Ctrl+C停止)")
            try:
                run_monitor()
            except KeyboardInterrupt:
                print("监控已停止")

        elif choice == '7':
            symbol = input("输入标的代码: ")
            market_type = monitor.detect_market_type(symbol)
            price = monitor.get_current_price(symbol, market_type)
            if price:
                print(f"{symbol} 当前价格: {price:.4f}")
            else:
                print(f"无法获取 {symbol} 价格")

        elif choice == '8':
            print("退出系统")
            break


if __name__ == "__main__":
    # 确保配置目录和文件存在
    setup_config_path()

    user_interface()